本文介绍Dockerfile中主要命令的用途
FROM
指定基础镜像,推荐的方式是 image:tag,精确指定。注意:可以使用多条FROM,这样就会构建多个镜像。
比如我同时想要busybox和nginx,我可以这么写
1 | FROM busybox:latest |
然后构建运行
1 | 构建 |
ENV和ARG
ENV为创建出来的容器声明环境变量。该环境变量在启动的容器中可用,在特定的指令中也能够使用,这些指令包括ENV、ADD、COPY、WORKDIR、EXPOSE、VOLUME、USER。
ARG类似,但它声明的变量只能在Dockerfile中使用,不能再启动的容器中使用。
我们做个实验,在docker build过程中输出定义的环境变量,在容器中输出该环境变量
1 | FROM busybox |
然后构建运行
1 | root@10-9-175-15:/home/ubuntu/docker-learn$ docker build -t busybox-env . |
RUN
RUN会在前一条命令创建出来的镜像的基础上创建一个容器,并在容器中运行命令,结束后将容器提交为新的镜像,新镜像作为下一条命令的基础。
RUN有两种格式
1 | # shell格式,通过/bin/sh -c运行的 |
几个注意点
- 推荐使用exec格式。
- exec格式中,
["ls", "-a", "-l"]
会被Docker当成json数组解析,因此必须使用双引号 - exec格式中,由于没有使用sh运行,因此环境变量是不会被解析的,除非你的可执行文件指的就是sh,即
["sh", "-c", "ls", "-al"]
这里就不演示了,在ENV我们演示过了,截取片段:如下,启动的临时容器id为3a9ca99e38c6,运行了echo后移除了该容器,并生成了id为79b312023015的层。
1 | Step 3/3 : RUN echo $ENVTEST |
COPY和ADD
这两个很相似,它们的共同点是:作用在本地文件时候,都是普通的复制关系,即将本地文件或文件夹复制到新镜像中;不同点是,ADD额外增加了两个功能,当源文件是URL时,它会下载下来,然后放到镜像中,当源文件是本地压缩文件时,它能将该文件解压然后复制到镜像中。
一般来说推荐使用COPY,因为它更方便理解。能用ADD的地方,完全可以用RUN加上wget或的命令完成。下面演示
1 | FROM busybox |
验证
1 | root@10-9-175-15:/home/ubuntu/docker-learn# docker build -t busybox-add-copy . |
可以看到
- Dockerfile被复制进来了
- namespace.c被复制进来了
- client.go被下载然后复制进来了
- test.tar.gz被解压成namespace.o并复制进来了
CMD和ENTRYPOINT
这两个必须一起提,因为很多时候开发者都搞不懂他们之间的关系
我们先看CMD,它有三种格式
1 | # shell格式 |
它的注意事项
- 一个Dockerfile中可以有多个CMD指令,但只有最后一个会生效
- CMD的前两种格式与RUN类似,但它本身的作用于RUN完全不同。它不会在容器构建过程中执行,而是在容器启动时作为第一条执行指令
- 如果用户在docker run时明确指定了指令,则会覆盖CMD指定的指令
再来看ENTRYPOINT,它只有两种格式
1 | # shell格式 |
它的注意事项
一个Dockerfile可以有多个ENTRYPOINT指令的,但只有最后一个会生效
使用shell格式时,ENTRYPOINT会忽略任何CMD和docker run指定的指令,并会运行在sh -c中。这意味着我们指定的进程PID将不会是1,不能接收Unix信号。在使用docker stop结束容器时,我们的进程收不到结束信号。
使用exec格式时,docker run传入的参数会覆盖CMD指定的内容,并附加到ENTRYPOINT指令的参数中。
也就是说,如果我有如下dockerfile声明
1
2CMD ["java", "-jar", "hello.jar"]
ENTRYPOINT ["sh", "-c"]不带任何参数启动容器时,实际执行的是:
sh -c java -jar hello.jar
。如果运行
docker run xxx ls
,实际执行的就是:sh -c ls
验证CMD
1 | FROM ubuntu |
运行
1 | root@10-9-175-15:/home/ubuntu/docker-learn# docker build -t ubuntu-cmd . |
可见:容器启动时默认运行了/bin/bash,并且,该进程号为1。
使用docker run进行覆盖
1 | 指定容器启动运行ls,这里看到,确实运行了ls,然后马上退出了容器,说明确实已经覆盖了CMD指令 |
验证ENTRYPOINT
1 | FROM ubuntu |
运行
1 | root@10-9-175-15:/home/ubuntu/docker-learn# docker build -t ubuntu-entrypoint . |
小问题
注意不同启动方式的影响,参考这篇文章
如何在容器启动时执行两条命令?
使用
shell
格式启动,比如CMD ["sh", "-c", "command1 && command2"]
要注意的是,command1必须是能结束的命令,否则command2不会执行。
<未完待续>
Dockerfile肯定不止这么写指令,但这几个是最重要的内容,能够正确实用Dockerfile的关键,其它等有需要时再添加
其它
几个原则和注意事项
- 共享Dockerfile比共享Docker镜像好,因为Dockerfile容易版本控制,构建过程清晰,占用空间少
- CMD指令和ENTRYPOINT并不存在取舍,二者结合使用最后。CMD由于可被docker run覆盖的特性,适用于指定可变参数,ENTRYPOINT相应地适用于指定不可变指令。
- 使用保证镜像尽量小
- 使用足够轻量的基础镜像
- 不要在镜像中放置无用的内容
- 如果有文件需要共享,用volume进行挂载,不要放到镜像中
- 充分利用缓存,Docker镜像时分层的,无论构建、拉取都会有缓存,减小Dockerfile的变化部分,有利于提升缓存命中率